#!/usr/bin/python
# -*- encoding: utf-8 -*-
#
# Copyright (C) 2008 Håvard Gulldahl.
#
# Licensed under the GNU Public License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.gnu.org/licenses/gpl-2.0.txt
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

__author__ = u'<havard@gulldahl.no> Håvard Gulldahl'
__license__ = 'GPL v2'
__version__ = '0.4' # sync with setup.py
#__debug__ = True

#import pdb
import sys, time, os.path
from cStringIO import StringIO
from email.utils import parseaddr
from PyQt4 import QtCore, QtGui, uic
import atom
try:
  import contactscopy as contacts
  import contactscopy.service as service
except ImportError:
  import gdata.contacts as contacts
  import gdata.contacts.service as service
import bitpimlite.vcard as bitpim
from gcontacts import vcard as VCard,  mscontact,  dialog as Dialog
import gcontacts


if True:
  import httplib
  httplib.HTTPConnection.debuglevel=1  ## verbosely output http traffic

UI_DIR = ''  
if True:
  UI_DIR = os.path.join('gcontacts',  'data')
  sys.path.insert(0,  'gcontacts')

PASS_FILE = 'gcontacts_pass.txt'

if sys.platform == 'win32':
  PASS_FILE = os.path.join(os.getenv('HOMEDRIVE'), os.getenv('HOMEPATH'),  '_gcontacts' )
else:
  PASS_FILE = os.path.join(os.path.expanduser('~'),  '.gcontactspass')
  
class RedirectStream:
    def __init__(self, stdout):
        self.stdout = stdout
        self.stream = StringIO.StringIO()

    def write(self, s):
        self.stream.write(s)
        self.stdout.write(s)

class GContacts(QtGui.QDialog):

  gmail = None
  loginFailed = True
  stdout = None
  vcard_mimetypes = ['text/directory; charset="utf-8"; profile="vCard"', 'text/x-vcard']
  
  def __init__(self):
    QtGui.QDialog.__init__(self)
    self.gui = uic.loadUi(os.path.join(UI_DIR, 'gcontacts.ui'), self)
    self.connect(self.gui.clearInput, QtCore.SIGNAL('clicked()'), self.clearInputBox)
    self.connect(self.gui.clearInput, QtCore.SIGNAL('clicked()'), self.login)
    self.connect(self.gui.Input, QtCore.SIGNAL('editingFinished()'), self.search)
    self.connect(self, QtCore.SIGNAL('loggedIn'), self.load)
    self.connect(self.gui.contactList, QtCore.SIGNAL('droppedVCard(PyQt_PyObject)'), self.newVCard)
    self.connect(self.gui.contactList, QtCore.SIGNAL('droppedUrl(PyQt_PyObject)'), self.newUrl)
    self.connect(self.gui.contactList, QtCore.SIGNAL('droppedAddress(PyQt_PyObject)'), self.newAddress)

  def login(self):
    try:
      user, pword = open(PASS_FILE).readline().split(',')
    except IOError:
      user, pword = self.loginDialog()
    if not user:
      self.alert('Username not supplied')
      return False
    if not pword:
      self.alert('Password not supplied')
      return False
    domain = 'lurtgjort.no'
    #TODO: add domain-apps support
    self.gmail = service.ContactsService(email=user, password=pword,
        source='lurtgjort.no-gcontacts-%s' % __version__)
    try:
      self.gmail.ProgrammaticLogin()
    except Exception, e:
      self.alert(unicode(e.message))
      raise
      return False
    else:
      self.loginFailed = False
      self.emit(QtCore.SIGNAL('loggedIn'))

  def load(self):
    print "load"
    if self.loginFailed: #not logged in
      return False

    self.contacts = self.gmail.GetContactsFeed('/m8/feeds/contacts/default/base?sortorder=descending&orderby=lastmodified&max-results=35')
    #pdb.set_trace()
    #print self.contacts
    num_contacts = len(self.contacts.entry)
    self.gui.contactList.clear() # zap the existing tree
    self.gui.progressBar.setMaximum(num_contacts) # init progbar
    self.gui.progressBar.setValue(0)
    for j, contact in enumerate(self.contacts.entry):
      if contact.deleted is not None:
        print "deleted"
        continue
      self.gui.progressBar.setValue(j)
      self.insertContact(contact)

  def insertContact(self, entry):
    row = QtGui.QTreeWidgetItem(self.gui.contactList)
    row.contact = entry # store the contact object
    row.setText(0, txt(row.contact.title))
    row.setText(1, ", ".join([ txt(e.address) for e in row.contact.email ]))
    self.gui.contactList.addTopLevelItem(row)

  def newVCard(self, data):
    print "new", data
    if self.loginFailed: #not logged in
      return False
    if not hasattr(data,  'read'):
      data = StringIO(data)
    vcards = VCard.VCards(bitpim.VFile(data))
    if __debug__:
      print vcards
    for vcard in vcards:
      vdata = vcard.getdata()
      print "==================bitpim.VCARD====================\n", vdata, "\n============================"
      rel = {'business': contacts.REL_WORK,
             'home': contacts.REL_HOME,
             'other': contacts.REL_OTHER}
      name = atom.Title(text=vcard.getfield('name').pop())

      emails = []
      first = True
      for email in vcard.getfield('email'):
        emails.append(contacts.Email(address=email,
          primary=(first and 'true') or None))
        first = False
      
      phones = []
      phone_rel = { 'business': contacts.PHONE_WORK,
                    'business fax': contacts.PHONE_WORK_FAX,
                    'cell': contacts.PHONE_MOBILE }
      first = True
      for tel in vcard.getfield('phone'):
        phones.append(contacts.PhoneNumber(text=tel['number'],
          rel=phone_rel.get(tel['type'], contacts.PHONE_GENERAL),
          primary=(first and 'true') or None))
        first = False

      addresses = []
      first = True
      for address in vcard.getfield('address'):
        addresses.append(contacts.PostalAddress(text=address['street'],
          rel=rel.get(address['type'], contacts.REL_OTHER),
          primary=(first and 'true') or None))
        first = False

      im_rel = {  'AIM': 'http://schemas.google.com/g/2005#AIM', # AOL Instant Messenger
                  'MSN': 'http://schemas.google.com/g/2005#MSN', #    MSN Messenger
                  'YAHOO': 'http://schemas.google.com/g/2005#YAHOO', #  Yahoo Messenger
                  'SKYPE': 'http://schemas.google.com/g/2005#SKYPE', #  Skype
                  'QQ': 'http://schemas.google.com/g/2005#QQ', #     QQ
                  'GOOGLE_TALK': 'http://schemas.google.com/g/2005#GOOGLE_TALK', # Google Talk
                  'ICQ': 'http://schemas.google.com/g/2005#ICQ', #    ICQ
                  'JABBER': 'http://schemas.google.com/g/2005#JABBER', #     Jabber
                }
      ims = []
      first = True
      for im in vcard.getfield('im'):
        _im = contacts.IM(address=im['address'],
          rel=rel.get(im['type'], contacts.REL_OTHER),
          primary=(first and 'true') or None)
        try:
          _im.protocol = im_rel[im['protocol']]
        except KeyError:
          _im.label = u'Protocol: %s' % im['protocol']
          _im.rel = None ## Cannot have both label and rel
        ims.append(_im)
        first = False

      comment = None
      #if vcard.getfield('uid'):
        #comment = atom.Content(text='UID=%s\n' % vcard.getfield('uid').pop())
      if vcard.getfield('notes'):
        comment = atom.Content(text=vcard.getfield('notes').pop())

      org = title = organiz = None
      if vcard.getfield('title'):
        title = contacts.OrgTitle(text=vcard.getfield('title').pop())
      if vcard.getfield('organisation'):
        org = contacts.OrgName(text=vcard.getfield('organisation').pop())
      if org or title:
        organiz = contacts.Organization(org_name = org, org_title=title)
        
      contact = contacts.ContactEntry(title=name, email=emails,
        phone_number=phones, postal_address=addresses, im=ims, content=comment,
        organization=organiz,
        )
      self.new(contact)
      
  def newUrl(self,  url):  # a QUrl object
    contactfile = unicode(url.toLocalFile()) # TODO: support for non-file:// urls
    if contactfile.endswith('.contact'): 
      print "new from vista contact"
      contact = mscontact.MSContactEntry()
      try:
        contact.loadMSContact(contactfile)
      except Exception, e:
        raise e
      self.new(contact)
    elif contactfile.endswith('.vcf'):
      print "new from VCard file"
      self.newVCard(file(contactfile).read())
    else:
      print "unknown url: ",  contactfile

  def newAddress(self,  address):
    "add new contact from 'name <address@example.com>' or 'address@example.com (name)'"
    for addr in address.split(','):
      try:
        name,  email = parseaddr(addr) # from email.utils
      except Exception,  e:
        raise e
      assert '@' in email,  "Invalid email address (no @ sign)"
      nom = atom.Title(text=name)
      mail = contacts.Email(address=email, primary='true')
      self.new(contacts.ContactEntry(title=nom, email=[mail]))

  def new(self,  contact):
    print "==================GContacts.Entry====================\n", contact, "\n============================"
    try:
      newEntry = self.gmail.CreateContact(contact, 
          '/m8/feeds/contacts/default/base')
    except Exception, e:
      ex = e.args[0]
      if ex.has_key('status') and ex['status'] == 409:
        # Conflict : A contact with this email already exists
        currentEntry = contacts.ContactEntryFromString(ex['body'].decode('utf8'))
        if self.conflictDialog(currentEntry, contact):
          updatedEntry = self.gmail.UpdateContact(currentEntry.GetEditLink().href,
                            contact)
          print updatedEntry
      else:
        self.alert(str(e))
      #self.alert('%s: %s\n%s' % (e.status, e.reason, e.body))
    else:
      print newEntry
      self.insertContact(newEntry)

  def clearInputBox(self):
    return self.gui.Input.clear()

  def search(self):
    query = unicode(self.gui.Input.text())
    print "searching for %s" % query

  def loginDialog(self):
    dialog = Dialog.PasswordDialog()

    res = dialog.exec_()
    if res == dialog.Accepted:
      return (unicode(dialog.user.text()),
        unicode(dialog.password.text()))
    else:
      raise Exception("Password dialog was cancelled")

  def conflictDialog(self, contact1, contact2):
    dialog = Dialog.ConflictDialog()

    dialog.contact1Label.setText(contact1.ToVCard().decode('utf8'))
    dialog.contact2Label.setText(contact2.ToVCard())
    res = dialog.exec_()
    return res == dialog.Accepted

  def alert(self, msg):
    QtGui.QMessageBox.critical(self, 'Failure', msg, QtGui.QMessageBox.Ok)
  
def txt(obj):
  'Extracts text from elementtree construct. Always returns utf8'
  if hasattr(obj, 'text'):
    obj = obj.text
  if obj is None: return u''
  return obj.decode('utf8')


if __name__ == "__main__":
  app = QtGui.QApplication(sys.argv)
  app.setStartDragDistance(10)
  gui = GContacts()
  gui.gui.show()
  gui.login()
  sys.exit(app.exec_())
